package com.xuecheng.manage_media.service;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.assertj.core.util.Arrays;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import com.alibaba.fastjson.JSON;
import com.xuecheng.framework.domain.media.MediaFile;
import com.xuecheng.framework.domain.media.response.CheckChunkResult;
import com.xuecheng.framework.domain.media.response.MediaCode;
import com.xuecheng.framework.exception.ExceptionCast;
import com.xuecheng.framework.model.response.CommonCode;
import com.xuecheng.framework.model.response.ResponseResult;
import com.xuecheng.manage_media.config.RabbitMQConfig;
import com.xuecheng.manage_media.dao.MediaFileRepository;

@Service
public class MediaUploadService {
    
    @Autowired
    MediaFileRepository mediaFileRepository;
    
    //读取配置文件中的信息
    @Value("${xc-service-manage-media.upload-location}")
    String upload_location;
    
    @Value("${xc-service-manage-media.mq.routingkey-media-video}")
    String routingkey_media_video;
    
    @Autowired
    RabbitTemplate rabbitTemplate;

    //文件上传前的注册，检查文件是否存在
    public ResponseResult register(String fileMd5, String fileName, Long fileSize,
            String mimetype, String fileExt) {
        // 检查文件在磁盘上是否存在
        //获取文件所属目录路径
        String fileFolderPath = this.getFileFolderRelativePath(fileMd5);
        //获取文件路径
        String filePath = this.getFilePath(fileMd5, fileExt);
        File file = new File(filePath);
        //检查文件是否存在
        boolean exist = file.exists();
        //检查mongoDB数据库中是否有记录
        Optional<MediaFile> optional = mediaFileRepository.findById(fileMd5);
        if(exist && optional.isPresent()) {
            //文件存在，抛出异常
            ExceptionCast.cast(MediaCode.UPLOAD_FILE_REGISTER_EXIST);
        }
        //文件不存在，做一些准备工作
        //检察目录是否存在，不存在则创建
        File folderFile = new File(fileFolderPath);
        if(!folderFile.exists()) {
            folderFile.mkdirs();//注意，是多级目录创建
        }
        return new ResponseResult(CommonCode.SUCCESS);
    }
    
    /**
      * 根据文件md5得到文件路径
      * 规则：
      * 一级目录：md5的第一个字符
      * 二级目录：md5的第二个字符
      * 三级目录：md5
      * 文件名：md5+文件扩展名
      * @param fileMd5 文件md5值
      * @param fileExt 文件扩展名
      * @return 文件路径
      */
    //私有方法，获取文件目录的路径
    private String getFileFolderRelativePath(String fileMd5) {
        String folderPath =  fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/"
                         + fileMd5 + "/";
        return folderPath;
    }
    
    //私有方法，获取文件的路径
    private String getFilePath(String fileMd5,String fileExt) {
        String filePath = upload_location + fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/"
                + fileMd5 + "/" + fileMd5 + "." + fileExt;
        return filePath;
    }
    

    /**
     * @Description (分块检查)
     * @param fileMd5 文件md5
     * @param chunk  块的下标，int型
     * @param chunkSize 块的代销
     * @return
     */
    public CheckChunkResult checkChunk(String fileMd5, Integer chunk, Integer chunkSize) {
        // 检查分块文件是否存在
        // 获取所在目录
        String chunkFolderPath = this.getChunkFolderPath(fileMd5);
        //块文件对象
        File chunkFile = new File(chunkFolderPath + chunk);
        if(chunkFile.exists()) {
            return new CheckChunkResult(CommonCode.SUCCESS, true);
        }else {
            return new CheckChunkResult(CommonCode.FAIL, false);
        }
    }
    
    //定义私有方法，获取分块文件所在目录
    private String getChunkFolderPath(String fileMd5) {
        String folderPath = upload_location + fileMd5.substring(0,1) + "/" + fileMd5.substring(1,2) + "/"
                         + fileMd5 + "/chunk/";
        return folderPath;
    }

    //上传分块
    public ResponseResult uploadChunk(MultipartFile file, Integer chunk, String fileMd5) {
        // 检查分块文件所在目录，不存在则创建
        String chunkFolderPath = this.getChunkFolderPath(fileMd5);
        File chunkFolderFile = new File(chunkFolderPath);
        if(!chunkFolderFile.exists()) {
            chunkFolderFile.mkdirs();
        }
        //获取分块文件路径
        String chunkFilePath = chunkFolderPath + chunk;
        //获取上传文件的输入流
        InputStream inputStream = null;
        FileOutputStream fileOutputStream = null;
        try {
            inputStream = file.getInputStream();
            fileOutputStream = new FileOutputStream(new File(chunkFilePath));
            //拷贝流
            IOUtils.copy(inputStream, fileOutputStream);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            //关闭流
            try {
                inputStream.close();
                fileOutputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return new ResponseResult(CommonCode.SUCCESS);
    }

    //合并分块文件
    public ResponseResult mergeChunk(String fileMd5, String fileName, Long fileSize, 
            String mimetype, String fileExt) {
        // 获取分块文件所属目录
        String chunkFolderPath = this.getChunkFolderPath(fileMd5);
        // 所属目录对象
        File chunkFolderFile = new File(chunkFolderPath);
        //获取分块文件列表
        File []files = chunkFolderFile.listFiles();
        //转为List
        List<Object> fileList = Arrays.asList(files);
        //合并文件路径
        String filePath = this.getFilePath(fileMd5, fileExt);
        //文件对象
        File mergeFile = new File(filePath);
        //执行合并
        mergeFile = this.mergeFile(fileList, mergeFile);
        if(mergeFile == null) {
            //合并失败，抛出异常
            ExceptionCast.cast(MediaCode.MERGE_FILE_FAIL);
        }
        //校验文件的md5值是否和前端传入的一样
        boolean md5Check = this.checkFileMd5(mergeFile, fileMd5);
        if(!md5Check) {
            //不一致，抛出异常
            ExceptionCast.cast(MediaCode.MERGE_FILE_CHECKFAIL);
        }
        //将文件信息保存到mongoDB
        MediaFile mediaFile = new MediaFile();
        mediaFile.setFileId(fileMd5);
        mediaFile.setFileName(fileMd5 + "." + fileExt);
        mediaFile.setFileOriginalName(fileName);
        //文件保存的相对路径
        String filePath2 = this.getFileFolderRelativePath(fileMd5);
        mediaFile.setFilePath(filePath2);
        mediaFile.setFileSize(fileSize);
        mediaFile.setUploadTime(new Date());
        mediaFile.setMimeType(mimetype);
        mediaFile.setFileType(fileExt);
        mediaFile.setFileStatus("301002");
        //保存
        mediaFileRepository.save(mediaFile);
        //向MQ发送消息
        this.sendProcessVideoMsg(mediaFile.getFileId());
        //然后删除分块文件，能合并成功，分块一定存在，不用作判断
        this.delFolderAndFile(chunkFolderFile);
        return new ResponseResult(CommonCode.SUCCESS);
    }
    
    //私有方法，发送视频处理信息
    private ResponseResult sendProcessVideoMsg(String mediaId) {
        //查询数据库
        Optional<MediaFile> optional = mediaFileRepository.findById(mediaId);
        if(!optional.isPresent()) {
            ExceptionCast.cast(CommonCode.FAIL);
        }
        //构建消息内容
        Map<String,String> map = new HashMap<>();
        map.put("mediaId", mediaId);
        //转换为字符串
        String jsonString = JSON.toJSONString(map);
        //向MQ发送消息
        try {
            rabbitTemplate.convertAndSend(RabbitMQConfig.EX_MEDIA_PROCESSTASK,
                    routingkey_media_video,jsonString);
        } catch (Exception e) {
            e.printStackTrace();
            return new ResponseResult(CommonCode.FAIL);
        }
        return new ResponseResult(CommonCode.SUCCESS);
    }
    
    //私有方法，校验文件是否为md5
    private boolean checkFileMd5(File mergeFile,String md5) {
        try {
            //创建文件输入流
            FileInputStream fileInputStream = new FileInputStream(mergeFile);
            //得到文件的md5
            String md5Hex = DigestUtils.md5Hex(fileInputStream);
            //和传入的md5比较
            if(md5Hex.equalsIgnoreCase(md5)) {
                return true;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }
    
    //私有方法，合并分块
    private File mergeFile(List<Object> chunkFileList,File mergeFile) {
        try {
            //如果合并文件存在则删除，不存在则创建
            if(mergeFile.exists()) {
                mergeFile.delete();
            }else {
                mergeFile.createNewFile();
            }
            //对块文件排序
            Collections.sort(chunkFileList,new Comparator<Object>() {

                //升序排列
                @Override
                public int compare(Object o1, Object o2) {
                    if(Integer.parseInt(((File)o1).getName()) >
                    Integer.parseInt(((File)o2).getName())) {
                        return 1;
                    }
                    return -1;
                }
            });
            //创建写对象
            RandomAccessFile raf_write = new RandomAccessFile(mergeFile,"rw");
            //缓冲区
            byte []bys = new byte[1024];
            for(Object obj:chunkFileList) {
                //Object转为File类型
                File chunkFile = (File) obj;
                //创建读对象
                RandomAccessFile raf_read = new RandomAccessFile(chunkFile,"r");
                int len = -1;
                //边读边写
                while((len = raf_read.read(bys))!=-1) {
                    //写入
                    raf_write.write(bys,0,len);
                }
                //关闭写对象
                raf_read.close();
            }
            //关闭读对写
            raf_write.close();
            return mergeFile;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    
    //私有方法，删除目录及目录下的所有文件
    private void delFolderAndFile(File folderFile) {
        File []files = folderFile.listFiles();
        for(File f:files) {//删除所有文件
            f.delete();
        }
        //最后删除目录
        folderFile.delete();
    }
     

}
